package de.fmannan.wildit.persistence;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedList;
import java.util.NoSuchElementException;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import de.fmannan.wildit.domainmodel.Alarm;
import de.fmannan.wildit.domainmodel.AlarmType;
import de.fmannan.wildit.domainmodel.CalendarConverter;

public class DatabaseAdapter implements PersistenceAdapter
{

    private static final String TAG = "DatabaseAdapter";

    // general database properties
    public static final String DATABASE_NAME = "wildit";
    public static final int DATABASE_VERSION = 1;
    private DatabaseHelper databaseHelper;
    private SQLiteDatabase database;

    // Properties of an alarm
    public static final String TABLE_ALARM = "Alarm";
    public static final String ID = "id";
    public static final String START_TIME = "start_time";
    public static final String END_TIME = "end_time";
    public static final String INTERVAL = "interval";
    public static final String IS_ENABLED = "is_enabled";
    private static final String ALARM_TYPE = "alarm-type";

    private static final String timeFormat = "HH:mm:ss";

    /**
     * Database creation sql statement.
     */
    private static final String DATABASE_CREATE = "create table " + TABLE_ALARM
	    + " (" + ID + " integer primary key autoincrement, " + START_TIME
	    + " text not null, " + END_TIME + " text not null, " + INTERVAL
	    + " text, " + IS_ENABLED + " text not null, " + ALARM_TYPE
	    + " text not null);";

    public static String getDatabaseCreate()
    {
	return DATABASE_CREATE;
    }

    private final Context context;

    public DatabaseAdapter(Context ctx)
    {
	this.context = ctx;
    }

    private synchronized DatabaseAdapter open() throws SQLException
    {
	databaseHelper = new DatabaseHelper(context);
	database = databaseHelper.getWritableDatabase();
	return this;
    }

    private synchronized void close()
    {
	databaseHelper.close();
    }

    public synchronized long addAlarm(Alarm alarm)
    {
	open();
	ContentValues values = new ContentValues();
	values.put(START_TIME, alarm.getStartTimeAsString());
	values.put(END_TIME, alarm.getEndTimeAsString());
	values.put(INTERVAL, alarm.getIntervalAsString());
	values.put(ALARM_TYPE, alarm.getTypeAsString());
	values.put(IS_ENABLED, alarm.isEnabled());

	return database.insert(TABLE_ALARM, null, values);
    }

    public Collection<Alarm> getAlarms(AlarmType filter) throws ParseException
    {
	return getAlarms(ALARM_TYPE, filter.toString());
    }

    @Override
    public Alarm getAlarm(int id) throws NoSuchElementException, ParseException
    {
	return getAlarms(ID, String.valueOf(id)).iterator().next();	    
    }
    
    @Override
    public synchronized Calendar getAlarmEndTime(int id)
    {
	return getTime(id, END_TIME);
    }

    @Override
    public synchronized Calendar getAlarmStartTime(int id)
    {
	return getTime(id, START_TIME);
    }

    @Override
    public synchronized Calendar getAlarmInterval(int id)
    {
	return getTime(id, INTERVAL);
    }

    /**
     * Retrieves the time from the row matching the given ID, from the specified
     * column.
     */
    private synchronized Calendar getTime(int id, String column)
    {
	//TODO can probably make use of getValue method
	open();
	Cursor cursor = database.rawQuery("select " + column + " from "
		+ TABLE_ALARM + " where id=?;", new String[] { String
		.valueOf(id) });
	try
	{
	    if (cursor != null && cursor.moveToFirst())
	    {
		return CalendarConverter.StringToCalendar(cursor.getString(0),
			timeFormat);
	    }
	} catch (java.text.ParseException exc)
	{
	    Log.e(TAG, "Failed to parse time");
	}
	close();
	// TODO Notify the user that the data could not be retrieved
	return null;
    }

    @Override
    public synchronized void setAlarmEndTime(int id, Calendar newTime)
    {
	updateTime(id, newTime, END_TIME);
    }

    @Override
    public synchronized void setAlarmInterval(int id, Calendar newTime)
    {
	updateTime(id, newTime, INTERVAL);
    }

    @Override
    public synchronized void setAlarmStartTime(int id, Calendar newTime)
    {
	updateTime(id, newTime, START_TIME);
    }

    /**
     * Stores the time from the newTime object into the given column of the
     * Alarm table.
     * 
     * @param ID
     * @param newTime
     * @param column
     */
    private synchronized void updateTime(int id, Calendar newTime, String column)
    {
	open();
	if (newTime != null)
	{

	    ContentValues update = new ContentValues();
	    update.put(column, newTime.get(Calendar.HOUR_OF_DAY) + ":"
		    + newTime.get(Calendar.MINUTE) + ":" + "00");

	    int result = database.update(TABLE_ALARM, update, ID + " == ?",
		    new String[] { String.valueOf(id) });

	    Log.d(TAG, "Rows affected by update of " + column + ":" + result);
	} else
	{
	    // TODO Notify the user by displaying a toast
	}
	close();
    }

    @Override
    public boolean isEnabled(int id)
    {
	// TODO we should throw an exception if not a boolean but null has been
	// retrieved
	return Boolean.valueOf(getValue(id, IS_ENABLED));
    }

    @Override
    public void setEnabled(int id, boolean value)
    {
	setValue(id, String.valueOf(value), IS_ENABLED);
    }

    private synchronized void setValue(int id, String value, String column)
    {
	open();
	ContentValues update = new ContentValues();
	update.put(column, value);

	int result = database.update(TABLE_ALARM, update, ID + " == ?",
		new String[] { String.valueOf(id) });

	Log.d(TAG, "Rows affected by update of " + column + ":" + result);
	close();
    }

    private synchronized String getValue(int id, String column)
    {
	open();
	Cursor cursor = database.rawQuery("select " + column + " from "
		+ TABLE_ALARM + " where id=?;", new String[] { String
		.valueOf(id) });

	if (cursor != null && cursor.moveToFirst())
	{
	    return cursor.getString(0);
	}

	close();
	// TODO Notify the user that the data could not be retrieved
	return null;
    }

    /**
     * @return a collection of alarm objects, matching the given filter. The
     *         filter will be constitute the where clause.
     */
    private synchronized Collection<Alarm> getAlarms(String filterName,
	    String filterValue) throws ParseException
    {
	open();
	Collection<Alarm> result = new LinkedList<Alarm>();
	Cursor cursor = database.rawQuery("select * from " + TABLE_ALARM
		+ " where " + filterName + "=?;", new String[] { filterValue });

	try
	{
	    if (cursor != null && cursor.moveToFirst())
	    {
		// Convert query result to alarm objects and add them to the
		// result
		// list
		while (cursor.moveToNext())
		{
		    int id = cursor.getInt(0);
		    Calendar start = CalendarConverter.StringToCalendar(cursor
			    .getString(1), timeFormat);
		    Calendar end = CalendarConverter.StringToCalendar(cursor
			    .getString(2), timeFormat);
		    Calendar interval = cursor.getString(3) != null ? CalendarConverter
			    .StringToCalendar(cursor.getString(3), timeFormat)
			    : null;
		    boolean enabled = Boolean.parseBoolean(cursor.getString(4));
		    AlarmType type = AlarmType.valueOf(cursor.getString(5));

		    result.add(new Alarm(id, start, end, interval, enabled,
			    type));
		}
	    }
	} 
	finally
	{
	    close();
	}
	
	return result;
    }

    public synchronized AlarmType getType(int id)
    {
	String result = getValue(id, ALARM_TYPE);
	// Try to convert it to an actual AlarmType
	return AlarmType.valueOf(result);
    }
}
